import re
import unittest

from mybatis_mapper2sql import convert
from mybatis_mapper2sql.convert import convert_parameters, convert_children

from utils import substitute_by_patterns

PAT_LIMIT = re.compile("limit\s+(\d+|\?)", re.IGNORECASE)

TBL_OR_FLD_PAT = re.compile("^[a-zA-Z_][a-zA-Z0-9_\.]*(\s+(asc|desc))?$", re.IGNORECASE)


def is_table_or_field(cont: str):
    return TBL_OR_FLD_PAT.match(cont.strip()) is not None


def _pre_process_when_otherwise_stmt(s: str):
    if not s:
        return "", s

    s = substitute_by_patterns(s, [(PAT_LIMIT, "")])

    s_upper = s.upper()
    for k in ("AND", "OR", "WHERE"):
        if s_upper.startswith(k):
            return k, s[len(k):]
    return "", s


def _decorate_when_otherwise_begin(s: str, first_when: bool):
    prefix, content = _pre_process_when_otherwise_stmt(s.strip().rstrip(";"))

    if is_table_or_field(content):
        # group-by/order-by can only keep one in choose-when-otherwise, we keep the first one
        if first_when:
            return " %s %s" % (prefix, content)
        else:
            return ""
    else:
        # where can use OR to mock choose-when-otherwise to involve as many as fields
        if first_when:
            return " %s %s" % (prefix, content)
        else:
            return " or %s" % content


def _decorate_when_otherwise_end(s: str):
    return "%s " % s


def _convert_choose_when_otherwise_fix(mybatis_mapper, child, **kwargs):
    # native
    native = kwargs.get("native")
    when_element_cnt = kwargs.get("when_element_cnt", 0)
    convert_string = ""
    for next_child in child:
        if next_child.tag == "when":
            if native and when_element_cnt >= 1:
                break
            else:
                test = next_child.attrib.get("test")
                convert_string += _decorate_when_otherwise_begin(
                    convert_parameters(next_child, text=True, tail=True),
                    when_element_cnt == 0,
                )
                # bugfix: 注释需换行，否则影响正常SQL解析
                convert_string += "-- if(" + test + ")\n"
                when_element_cnt += 1
                kwargs["when_element_cnt"] = when_element_cnt
                convert_string += _decorate_when_otherwise_end(
                    convert_children(mybatis_mapper, next_child, **kwargs)
                )
        elif next_child.tag == "otherwise":
            convert_string += _decorate_when_otherwise_begin(
                convert_parameters(next_child, text=True, tail=True), False
            )
            # bugfix: 注释需换行，否则影响正常SQL解析
            convert_string += "-- otherwise\n"
            convert_string += _decorate_when_otherwise_end(
                convert_children(mybatis_mapper, next_child, **kwargs)
            )
        else:
            convert_string += convert_children(mybatis_mapper, next_child, **kwargs)

    # bugfix: 获取跟在</choose>后的文本
    convert_string += convert_parameters(child, text=False, tail=True)
    return convert_string


def patch():
    convert.convert_choose_when_otherwise = _convert_choose_when_otherwise_fix


PAT_END_WHERE = re.compile("\\bENDWHERE\\b", re.IGNORECASE)
PAT_CONCAT_SPEC = re.compile('"%"\?"%"')


def post_process(sql: str):
    return substitute_by_patterns(
        sql, [(PAT_END_WHERE, "END WHERE"), (PAT_CONCAT_SPEC, "CONCAT('%', ?, '%')")]
    )


class MybatisPatchTest(unittest.TestCase):
    def test_sub_by_patterns(self):
        self.assertEqual(substitute_by_patterns("", [(re.compile("\w+"), "")]), "")
        self.assertEqual(
            substitute_by_patterns("abc123", [(re.compile("\d+"), "")]), "abc"
        )
        self.assertEqual(substitute_by_patterns("abc123", []), "abc123")

    def test_pre_process_when_otherwise_stmt(self):
        self.assertEqual(_pre_process_when_otherwise_stmt("and A=1"), ("AND", " A=1"))
        self.assertEqual(
            _pre_process_when_otherwise_stmt("where A>1 LIMIT 100"), ("WHERE", " A>1 ")
        )
        self.assertEqual(
            _pre_process_when_otherwise_stmt("AND A>1 LIMIT ?"), ("AND", " A>1 ")
        )

    def test_is_table_or_field(self):
        self.assertTrue(is_table_or_field("comm.id "))
        self.assertTrue(is_table_or_field("comm.id asc"))
        self.assertTrue(is_table_or_field("comm.id desc"))
        self.assertTrue(is_table_or_field("create_date desc"))
        self.assertFalse(is_table_or_field("comm.id < 1"))
        self.assertFalse(is_table_or_field("comm.id = 1"))
        self.assertFalse(is_table_or_field("comm.id between 1 and 2"))

    def test_post_process(self):
        self.assertEqual(
            post_process('select 1 from t where t.des like "%"?"%"'),
            "select 1 from t where t.des like CONCAT('%', ?, '%')",
        )

        self.assertEqual(
            post_process(
                "select case when A=? then B=? when A=? then B=? endwhere 1=1"
            ),
            "select case when A=? then B=? when A=? then B=? END WHERE 1=1",
        )


if __name__ == "__main__":
    unittest.main()
